/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/stars
*/

#include "simodo/module/CodeSupplier.h"

#include "simodo/ast/generator/FormationFlow.h"
#include "simodo/parser/fuze/FuzeRdp.h"
#include "simodo/parser/automaton/PushdownAutomaton.h"
#include "simodo/interpret/Interpret.h"
#include "simodo/interpret/SemanticOperationsEnumsLoader.h"
#include "simodo/interpret/builtins/hosts/fuze/FuzeRunning.h"
#include "simodo/interpret/builtins/hosts/base/BaseRunning.h"
#include "simodo/interpret/builtins/modules/LexicalParametersModule.h"
#include "simodo/interpret/builtins/modules/AstFormationModule.h"
#include "simodo/interpret/builtins/AstFormationBuilder.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/inout/format/fmt.h"

#if __cplusplus >= __cpp_2017
#include <filesystem>
namespace fs = std::filesystem;
#else
#include <experimental/filesystem>
namespace fs = std::filesystem::experimental;
#endif

#include <cassert>

namespace simodo::module
{
    namespace 
    {
        static parser::Grammar EMPTY_GRAMMAR_TABLE;
    }

    CodeSupplier::CodeSupplier( inout::Reporter_abstract & m,
                                 std::vector<std::string> grammar_places,
                                 inout::InputStreamSupplier_interface & stream_supplier,
                                 parser::SyntaxDataCollector_interface & syntax_data_collector)
        : _m(m)
        , _grammar_places(grammar_places)
        , _stream_supplier(stream_supplier)
        , _syntax_data_collector(syntax_data_collector)
    {
    }

    const ast::Node * CodeSupplier::getCode(const std::string & module_name,
                                            const inout::uri_set_t & files)
    {
        _last_error = {};
        
        {
            auto it = _codes.find(module_name);
            if (it != _codes.end())
                return &(it->second);
        }

        fs::path    path_to_module = module_name;
        std::string grammar_name   = path_to_module.extension().string().substr(1);
        std::string grammar_file   = findGrammarFile(grammar_name);

        if (grammar_file.empty()) {
            _last_error = inout::fmt("Unable to find grammar '%1'").arg(grammar_name);
            return nullptr;
        }

        std::shared_ptr<inout::InputStream_interface> in = _stream_supplier.supply(module_name);

        if (!in) {
            _last_error = inout::fmt("Unable to load file '%1'").arg(module_name);
            return nullptr;
        }

        inout::uri_index_t uri_index = files.empty() ? 0 : files.size()-1;

        auto [it, success] = _codes.insert({module_name,{}});
        
        bool ok = parse(files,uri_index,*in,grammar_file,it->second);

        if (!ok) {
            _codes.erase(it);
            /// \note В случае ошибки удалять файл не нужно, иначе может быть некорректной 
            /// диагностика.
            // files.pop_back();
            return nullptr;
        }

        return &(it->second);
    }

    std::string CodeSupplier::findGrammarFile(const std::string & grammar_name) const
    {
        for(const std::string & dir_name : _grammar_places) {
            fs::path    path_to_grammar = dir_name;

            path_to_grammar /= grammar_name + ".fuze"; 

            if (fs::exists(path_to_grammar)) 
                return path_to_grammar.string();
        }

        return {};
    }

    bool CodeSupplier::parse( const inout::uri_set_t & files,
                              inout::uri_index_t uri_index, 
                              inout::InputStream_interface & stream, 
                              const std::string & grammar_file, 
                              ast::Node & code)
    {
        /// \todo Необходимо сделать приём константной ссылки на грамматику 
        /// + поправить конструктор parser::PushdownAutomaton
        parser::Grammar g = getGrammarTable(grammar_file);
        if (g.rules.empty())
            return false;

        std::string file_name = uri_index < files.size() ? files[uri_index] : "?";

        variable::VariableSet_t hosts = interpret::loadSemanticOperationsEnums(fs::path(grammar_file).parent_path().string());

        ast::Node                       null_node;
        interpret::Interpret            inter(interpret::InterpretType::Preview, _m, _loom, {file_name}, null_node);
        interpret::builtins::BaseInterpret_abstract * 
                                        sbl = new interpret::builtins::BaseRunning(&inter);
        interpret::builtins::AstFormationBuilder  
                                        builder(_m,*sbl,hosts,_syntax_data_collector);
        parser::PushdownAutomaton       parser(_m, g, files, builder);

        inter.instantiateSemantics({sbl});

        if (!parser.parse(stream))
            return false;

        const_cast<ast::Node &>(builder.ast().tree().root()).swap(code);
        return true;
    }

    const parser::Grammar & CodeSupplier::getGrammarTable(const std::string & grammar_file)
    {
        {
            const auto it = _grammar_tables.find(grammar_file);
            if (it != _grammar_tables.end())
                return it->second;
        }

        parser::Grammar     grammar_table;

        if (parser::loadGrammarDump(grammar_file,grammar_table)) {
            auto [it, ok] = _grammar_tables.insert({grammar_file,grammar_table});
            return ok ? it->second : EMPTY_GRAMMAR_TABLE;
        }

        ast::FormationFlow  flow;
        parser::FuzeRdp     fuze(_m, grammar_file, flow);
        
        if (!fuze.parse())
            return EMPTY_GRAMMAR_TABLE;

        if (!buildGrammarTable(fs::path(grammar_file).stem().string(),
                               flow.tree().files(), 
                               flow.tree().root(),
                               parser::TableBuildMethod::SLR,
                               grammar_table)) {
            if (!buildGrammarTable(fs::path(grammar_file).stem().string(),
                                   flow.tree().files(), 
                                   flow.tree().root(),
                                   parser::TableBuildMethod::LR1,
                                   grammar_table))
                return EMPTY_GRAMMAR_TABLE;
        }

        for(const auto & [name, node] : grammar_table.handlers)
            /// \todo Для работы с грамматикой ode используется вставка кода "on_Start"
            /// Нужно будет её тоже прогонять!
            if (name == u"lex") {
                interpret::Interpret *  inter   = new interpret::Interpret(interpret::InterpretType::Preview, _m, _loom, flow.tree().files(), node);
                interpret::builtins::BaseRunning *   
                                    lex_runner  = new interpret::builtins::BaseRunning(inter);
                std::shared_ptr<interpret::builtins::LexicalParametersModule> 
                                    lex         = std::make_shared<interpret::builtins::LexicalParametersModule>(
                                                                                            grammar_table.lexical);

                /// \todo Пересмотреть странную передачу самого себя в свой же метод.
                /// PVS Studio: warn V678 An object is used as an argument to its own method.
                /// Consider checking the first actual argument of the 'instantiate' function.
                lex_runner->importNamespace(u"lex", lex->instantiate(lex), inout::null_token_location);
                inter->instantiateSemantics({lex_runner});
                _loom.dock(inter, true);
                _loom.stretch(inter);
            }

        _loom.finish();

        parser::saveGrammarDump(grammar_file,grammar_table);

        auto [it, ok] = _grammar_tables.insert({grammar_file,grammar_table});
        return ok ? it->second : EMPTY_GRAMMAR_TABLE;
    }

    bool CodeSupplier::buildGrammarTable(const std::string & grammar_name,
                                          const inout::uri_set_t & fuze_files, 
                                          const ast::Node & fuze_code, 
                                          parser::TableBuildMethod method,
                                          parser::Grammar & grammar_table)
    {
        interpret::Interpret    inter(interpret::InterpretType::Preview, _m, _loom, fuze_files, fuze_code,
                                    {
                                        new interpret::builtins::FuzeRunning(
                                                &inter,
                                                grammar_name,
                                                grammar_table,
                                                method),
                                    });

        _loom.stretch(&inter);
        _loom.finish();

        grammar_table.files = fuze_files;

        return !inter.errors();
    }

}
